package core

import (
	"context"
	"fmt"
	"runtime"
	"strconv"
	"strings"

	"github.com/bufbuild/protovalidate-go"
	"github.com/bufbuild/protovalidate-go/legacy"
	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport"
	"google.golang.org/protobuf/proto"

	"gitee.com/danqingzhao/go-dango/pkg/proto/auth"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
)

func Recovery(logHelper *log.Helper) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req any) (reply any, err error) {
			defer func() {
				if rerr := recover(); rerr != nil {
					buf := make([]byte, 64<<10)
					n := runtime.Stack(buf, false)
					buf = buf[:n]
					logHelper.WithContext(ctx).Errorf("%v: %+v\n%s\n", rerr, req, buf)
					err = errors.InternalServer(settings.ErrorReason_PANIC_ERROR.String(), "panic")
				}
			}()
			return handler(ctx, req)
		}
	}
}

func CORSMiddleware(c *settings.CORSConf, logHelper *log.Helper) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req any) (any, error) {
			tr, _ := transport.FromServerContext(ctx)
			tr.ReplyHeader().Set("Access-Control-Allow-Origin", strings.Join(c.AllowOrigin, ","))
			tr.ReplyHeader().Set("Access-Control-Allow-Methods", strings.Join(c.AllowMethods, ","))
			tr.ReplyHeader().Set("Access-Control-Allow-Headers", strings.Join(c.AllowHeaders, ","))
			tr.ReplyHeader().Set("Access-Control-Expose-Headers", strings.Join(c.ExposeHeaders, ","))
			tr.ReplyHeader().Set("Access-Control-Max-Age", strconv.Itoa(int(c.MaxAge)))
			tr.ReplyHeader().Set("Access-Control-Allow-Credentials", strconv.FormatBool(c.AllowCredentials))
			return handler(ctx, req)
		}
	}
}

func AuthMiddleware(logHelper *log.Helper, enforcer *auth.CasbinEnforce) middleware.Middleware {
	return func(h middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req any) (any, error) {
			tr, _ := transport.FromServerContext(ctx)
			grpcUrl := tr.Operation()
			userClaims, pErr := auth.ParseToken(tr.RequestHeader().Get("Authorization"))
			if pErr != nil {
				logHelper.Error(pErr.Error())
				return nil, errors.Unauthorized(auth.ErrorReason_UNAUTHENTICATION.String(), pErr.Error()).WithCause(pErr)
			}
			if userClaims.Subject == auth.JWTSubject_REFRESH.String() {
				return nil, errors.Unauthorized(
					auth.ErrorReason_UNAUTHENTICATION.String(),
					"refresh token cannot be used as access token",
				)
			}
			ptype := auth.PolicyRoleSub(enforcer.Daemon, userClaims.RoleId)
			ok, err := enforcer.HasPermission(ptype, grpcUrl, "GRPC")
			if err != nil {
				logHelper.Error(err.Error())
				return nil, errors.InternalServer(auth.ErrorReason_CASBIN_EXECUTION_POLICY_ERROR.String(), err.Error()).WithCause(err)
			}
			if !ok {
				return nil, errors.Forbidden(auth.ErrorReason_FORBIDDEN.String(), "forbidden")
			}
			return h(ctx, req)
		}
	}
}

func ProtoValidate() middleware.Middleware {
	validator, err := protovalidate.New(
		// Some projects may still be using PGV, turn on legacy support to handle this.
		legacy.WithLegacySupport(legacy.ModeMerge),
	)
	if err != nil {
		panic(fmt.Errorf("protovalidate.New() error: %w", err))
	}

	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req any) (reply any, err error) {
			if msg, ok := req.(proto.Message); ok {
				if err := validator.Validate(msg); err != nil {
					return nil, errors.BadRequest(settings.ErrorReason_VALIDATOR.String(), err.Error()).WithCause(err)
				}
			}
			return handler(ctx, req)
		}
	}
}

// func TransMiddleware(logHelper *log.Helper) middleware.Middleware {
// 	return func(h middleware.Handler) middleware.Handler {
// 		return func(ctx context.Context, req any) (any, error) {
// 			tr, _ := transport.FromServerContext(ctx)
// 			reply, err := h(ctx, req)
// 			if err == nil {
// 				return reply, nil
// 			}
// 			lang := tr.RequestHeader().Get("Accept-Language")
// 			if lang == "" {
// 				return reply, err
// 			}
// 			rErr := reason.FromError(err)
// 			msg, tErr := TranslateWithLang(lang, rErr.Reason, rErr.Metadata)
// 			if tErr != nil {
// 				logHelper.Warn(tErr.Error())
// 				return reply, rErr
// 			}
// 			rErr.Message = msg
// 			return reply, rErr
// 		}
// 	}
// }
